package com.fmdj.odr.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.fmdj.common.exception.GlobalException;
import com.fmdj.common.util.PageUtils;
import com.fmdj.common.wxpay.MyWXPayConfig;
import com.fmdj.common.wxpay.WXPay;
import com.fmdj.common.wxpay.WXPayUtil;
import com.fmdj.odr.controller.form.TransferForm;
import com.fmdj.odr.db.dao.OrderBillDao;
import com.fmdj.odr.db.dao.OrderDao;
import com.fmdj.odr.db.pojo.OrderBillEntity;
import com.fmdj.odr.db.pojo.OrderEntity;
import com.fmdj.odr.feign.DrServiceApi;
import com.fmdj.odr.quartz.HandleProfitsharingJob;
import com.fmdj.odr.quartz.QuartzUtil;
import com.fmdj.odr.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderDao orderDao;

    @Autowired
    private OrderBillDao orderBillDao;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private DrServiceApi drServiceApi;
    
    @Autowired
    private QuartzUtil quartzUtil;

    @Autowired
    private MyWXPayConfig wxPayConfig;
    @Autowired
    private MyWXPayConfig myWXPayConfig;

    @Override
    public HashMap searchOrderContent(long orderId) {
        HashMap hashMap = orderDao.searchOrderContent(orderId);
        //因为从数据库中取到的是json字符串 需要转换成json对象
        JSONObject startPlaceLocation = JSONUtil.parseObj(MapUtil.getStr(hashMap, "startPlaceLocation"));
        JSONObject endPlaceLocation = JSONUtil.parseObj(MapUtil.getStr(hashMap, "endPlaceLocation"));
        //将hashMap中的startPlaceLocation字段替换成json对象
        hashMap.put("startPlaceLocation", startPlaceLocation);
        hashMap.put("endPlaceLocation", endPlaceLocation);
        return hashMap;
    }


    @Override
    public HashMap selectDriverTodayBusinessData(Long driverId) {
        //  查询司机今日的订单数据
        HashMap hashMap = orderDao.selectDriverTodayBusinessData(driverId);

        //将数据取出进行处理 因为duration和income都是sum求和的值 若为0 则为null(不存在键和值的信息) 我们需要显示为0 所以需要处理
        //查询结果 null, null, 0
        String duration = MapUtil.getStr(hashMap, "duration");
        if (duration==null) {
            //生产时候请使用下面代码
            duration = "0";
            //测试时候请使用下面代码
            duration = "23";
        }

        hashMap.put("duration", duration); //因为duration和income都是sum求和的值 若为0 则为null(不存在键和值的信息)所以要put而不是replace
        String income = MapUtil.getStr(hashMap, "income");
        if (income==null) {
            //生产时候请使用下面代码
            income = "0.00";
            //测试时候请使用下面代码
            income = "378.00";
        }
        hashMap.put("income", income);

        hashMap.put("orders", 31);

        return hashMap;
    }

    @Override
    @Transactional
    @LcnTransaction
    public String insertOrder(OrderEntity entity, OrderBillEntity billEntity){
        //向order表插入数据
        int rows = orderDao.insert(entity);
        //说明添加成功
        if (rows == 1) {
            //查询orderId
            String orderId = orderDao.selectOrderIdByUUID(entity.getUuid());
            //订单明细表关联订单表
            billEntity.setOrderId(Long.parseLong(orderId));
            //向order_bill表插入数据
            rows = orderBillDao.insert(billEntity);

            /*
            * //将订单插入到redis，后期司机抢单的时候，可以基于redis的事务来避免同时多个司机枪单成功。
            * 并且设置抢单缓存的过期时间2分钟  前端倒计时时间小于缓存时长 方便区分是缓存过期自动删还是司机抢单成功删了
            * */
            if (rows == 1) {
                redisTemplate.opsForValue().set("order#"+orderId,orderId,2,TimeUnit.MINUTES);
                return orderId;
            }else {
                throw new GlobalException("订单明细表保存数据失败");
            }
        }else {
            throw new GlobalException("订单表保存数据失败");
        }
    }

    @Override
    @Transactional
    @LcnTransaction
    public String acceptOrder(Long orderId, Long driverId) {
        //Redis不存在抢单的订单编号,直接返回抢单失败
        if (!redisTemplate.hasKey("order#" + orderId)) {
            return "接单失败";
        }

        //Redis事务机制实现 防止超卖
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                //相当于(获取)记录下数据的版本号:假设A,B来抢单都记录1
                operations.watch("order#" + orderId);

                //开启redis事务
                operations.multi();

                //设置订单缓存的value, order#orderId=driverId
                operations.opsForValue().set("order#" + orderId, driverId);

                //如果B成功提交,导致redis记录版本号变为2,B抢单成功
                //A获取(watch)的版本号与redis记录的版本号不一致了,提交失败,会抛异常
                return operations.exec();
            }
        });

        //一旦抢单成功,删除redis中的订单,避免其它司机抢单
        redisTemplate.delete("order#" + orderId);

        //抢单成功,更新tb_order中的数据
        HashMap<Object, Object> map = new HashMap<>() {{
            put("orderId", orderId);
            put("driverId", driverId);
        }};

        int rows = orderDao.acceptNewOrder(map);

        if (rows!=1) {
            throw new GlobalException("接单失败,无法更新数据");
        }

        //抢单成功
        return "接单成功";
    }

    @Override
    public HashMap searchDriverExecuteOrder(Map map) {
        return orderDao.searchDriverExecuteOrder(map);
    }

    @Override
    public Integer searchOrderStatus(Map map) {
        //查询订单状态 若不存在设置为0返回给前端 方便处理
        Integer status = orderDao.searchOrderStatus(map);   //int 默认值为0 Integer 默认值为null
        if (status==null) {
            status = 0;
        }
        return status;
    }


    //如果关闭订单的Ajax请求发送给后端，这时候抢单缓存还存在，说明没有司机抢单，那么我们就删除抢单缓存和订单记录就可以了。
    //如果抢单缓存不存在，说明已经有司机抢单成功了，这时候我们返回给乘客端关闭订单失败，不删除该缓存。
    @Override
    @Transactional
    @LcnTransaction
    public String deleteUnAcceptOrder(Map map) {
        //先判断redis中是否存在该订单 若不存在说明订单已被抢单 则不能删除 若存在则删除(先在redis中将订单与司机解绑再删除缓存 删除订单 删除账单)
        Long orderId = MapUtil.getLong(map, "orderId");

        //如果不存在,说明有司机抢单成功,不再删除该订单,直接返回
        if (!redisTemplate.hasKey("order#" + orderId)) {
            return "订单取消失败";
        }

        //如果存在,说明没有司机抢单
        //将以下操作放在事务中,如果司机端能够解除与订单的绑定关系,再删除该订单
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                operations.watch("order#" + orderId);
                operations.multi();
                operations.opsForValue().set("order#" + orderId, "none");
                return operations.exec();
            }
        });

        //删除抢单缓存
        redisTemplate.delete("order#" + orderId);

        //删除数据库中订单信息
        int rows = orderDao.deleteUnAcceptOrder(map);
        if (rows != 1) {
            return "订单取消失败";
        }

        //删除数据库中账单信息
        rows = orderBillDao.deleteUnAcceptOrderBill(orderId);
        if (rows != 1) {
            return "订单取消失败";
        }

        return "订单取消成功";
    }

    @Override
    public HashMap searchDriverCurrentOrder(long driverId) {
        return orderDao.searchDriverCurrentOrder(driverId);
    }

    @Override
    public HashMap hasCustomerCurrentOrder(long customerId) {
        //构造结果集
        HashMap result = new HashMap();

        //查询未接受的订单
        HashMap hashMap = orderDao.hasCustomerUnAcceptOrder(customerId);
        result.put("hasCustomerUnAcceptOrder", hashMap != null);  //这段代码是一个标志，当不为空时，说明有未接受订单 否则没有未接受订单 当没有未接收订单时时不会把这条语句放进结果集中
        result.put("unAcceptOrder", hashMap);

        Long id = orderDao.hasCustomerUnFinishedOrder(customerId);
        result.put("hasCustomerUnFinishedOrder", id != null); //起到判断的作用,当id不为null时,说明有未完成订单并且这条代码不会放进结果集返回
        result.put("unFinishedOrder", id);

        return result;
    }

    @Override
    public HashMap searchOrderForMoveById(Map map) {
        return orderDao.searchOrderForMoveById(map);
    }

    @Override
    @Transactional
    @LcnTransaction
    public int arriveStartPlace(Map map) {
        Long orderId = MapUtil.getLong(map, "orderId");
        redisTemplate.opsForValue().set("order_driver_arrived#" + orderId, "1");
        int rows = orderDao.updateOrderStatus(map);
        return rows;
    }

    @Override
    public Boolean confirmArriveStartPlace(long orderId) {
        if (redisTemplate.hasKey("order_driver_arrived#" + orderId) && redisTemplate.opsForValue().get("order_driver_arrived#" + orderId).toString().equals("1")) {
            redisTemplate.opsForValue().set("order_driver_arrived#" + orderId, "2");
            return true;
        }
        return false;
    }

    @Override
    @Transactional
    @LcnTransaction
    public int startDriving(Map map) {
        Long orderId = MapUtil.getLong(map, "orderId");
        String key = "order_driver_arrived#" + orderId;
        if (redisTemplate.hasKey(key) && redisTemplate.opsForValue().get(key).toString().equals("2")) {
            redisTemplate.delete(key);

            int rows = orderDao.updateOrderStatus(map);
            if (rows!=1) {
                throw new GlobalException("更新订单状态失败");
            }
            return rows;
        }
        return 0;
    }

    @Override
    @Transactional
    @LcnTransaction
    public int updateOrderStatus(Map map) {
        int rows = orderDao.updateOrderStatus(map);
        if (rows!=1) {
            throw new GlobalException("更新订单状态失败");
        }
        return rows;
    }

    @Override
    public PageUtils searchOrderByPage(Map map) {
        Long count = orderDao.searchOrderCount(map);
        ArrayList list = null;

        if (count == null) {
            list = new ArrayList();
        }else {
            list = orderDao.searchOrderByPage(map);
        }

        Integer start = MapUtil.getInt(map, "start");
        Integer length = MapUtil.getInt(map, "length");

        return new PageUtils(list,count,start,length);
    }


    @Override
    public ArrayList<HashMap> searchOrderStartLocationIn30Days() {
        ArrayList<String> list = orderDao.searchOrderStartLocationIn30Days();
        //构造返回结果集  里面放着的一个个hashMap hashMap中是 {"latitude": "40.22", "longitude": "116.24"}
        ArrayList<HashMap> result = new ArrayList<>();

        //对list进行遍历处理 去掉经纬度的后四位
        list.forEach(location -> {
            //因为从数据库中拿到的是json字符串,所以需要用JSONObject转化为json对象,方便取出值(根据键取出值) {"latitude": "40.224406", "longitude": "116.247649"}
            JSONObject jsonObject = JSONUtil.parseObj(location);
            String latitude = jsonObject.getStr("latitude");
            String longitude = jsonObject.getStr("longitude");
            //截取经纬度 40.224406 -> 40.22 再拼接0001
            latitude = latitude.substring(0, latitude.length() - 4);  // 索引包含头不包尾
            latitude +=  "0001";
            longitude = longitude.substring(0, longitude.length() - 4);
            longitude +=  "0001";
            HashMap map = new HashMap<>();
            map.put("latitude", latitude);
            map.put("longitude", longitude);
            result.add(map);
        });
        return result;
    }

    @Override
    public Boolean validDriverOwnOrder(Map map) {
        long count = orderDao.validDriverOwnOrder(map);
        return count == 1 ? true : false;
    }

    @Override
    public HashMap searchSettlementNeedData(long orderId) {
        HashMap hashMap = orderDao.searchSettlementNeedData(orderId);
        return hashMap;
    }

    @Override
    public HashMap searchOrderById(Map map) {
        HashMap hashMap = orderDao.searchOrderById(map);
        String startPlaceLocation = MapUtil.getStr(hashMap, "startPlaceLocation");
        String endPlaceLocation = MapUtil.getStr(hashMap, "endPlaceLocation");
        //将map中的经纬度(String)转化成json对象 目的：方便前端处理
        hashMap.replace("startPlaceLocation", JSONUtil.parseObj(startPlaceLocation));
        hashMap.replace("endPlaceLocation", JSONUtil.parseObj(endPlaceLocation));
        return hashMap;
    }

    @Override
    public HashMap validCanPayOrder(Map map) {
        HashMap hashMap = orderDao.validCanPayOrder(map);
        if (CollectionUtil.isEmpty(hashMap)) {
            throw new GlobalException("该订单不可支付");
        }
        return hashMap;
    }

    @Override
    public int updateOrderPrepayId(Map map) {
        int rows = orderDao.updateOrderPrepayId(map);
        if (rows != 1) {
            throw new GlobalException("更新订单预支付id失败");
        }
        return rows;
    }

    /*支付成功后进行的业务逻辑*/
    @Override
    public void handlePayment(String uuid, String driverOpenId, String payId, String payTime) {
        //更新订单状态之前先查询订单状态
        //若订单状态为6  则更新订单状态为7 若订单状态为7 则什么都不做 直接返回
        HashMap hashMap = orderDao.searchOrderIdAndStatus(uuid);
        Integer status = MapUtil.getInt(hashMap, "status");
        if (status==7) {
            return;
        }
        
        //更新订单状态,订单支付时间以及订单id
        HashMap<Object, Object> map = new HashMap<>() {{
            put("uuid", uuid);
            put("payId", payId);
            put("payTime", payTime);
        }};
        int rows = orderDao.updateOrderPayIdAndStatus(map);
        if (rows !=1) {
            throw new GlobalException("更新订单状态失败");
        }

        //查询订单的系统奖励费以及司机id
        hashMap = orderDao.searchDriverIdAndIncentiveFee(uuid);
        String incentiveFee = MapUtil.getStr(hashMap, "incentiveFee");
        Long driverId = MapUtil.getLong(hashMap, "driverId");
        //如果系统奖励费大于0 则进行系统奖励
        if (new BigDecimal(incentiveFee).compareTo(new BigDecimal("0.00"))==1) {
            TransferForm transferForm = new TransferForm();
            transferForm.setAmount(incentiveFee);
            transferForm.setDriverId(driverId);
            transferForm.setType((byte) 2);
            transferForm.setUuid(uuid);
            transferForm.setRemark("系统奖励费");
            drServiceApi.transfer(transferForm);
        }

        //执行订单分账
        //执行分账之前判断是否创建了分账定时器
        if (quartzUtil.checkExists(uuid, "代驾单分账任务组") ||
                quartzUtil.checkExists(uuid, "查询代驾单分账任务组")) {
            //说明存在分账定时器 则不需要重复创建
            return;
        }
        
        //新建jobdetail对象放入分账的业务数据
        JobDetail jobDetail = JobBuilder.newJob(HandleProfitsharingJob.class).build();
        JobDataMap dataMap = jobDetail.getJobDataMap();
        dataMap.put("uuid", uuid);
        dataMap.put("payId", payId);
        dataMap.put("driverOpenId", driverOpenId);
        DateTime executeTime = new DateTime().offset(DateField.MINUTE, 2);//当前时间两分钟后执行分账
        quartzUtil.addJob(jobDetail, uuid, "代驾单分账任务组", executeTime);

        //完成订单(更改订单状态)
        rows = orderDao.finishOrder(uuid);
        if (rows!=1) {
            throw new GlobalException("更新订单状态失败");
        }
    }

    @Override
    @LcnTransaction
    @Transactional
    public String updateOrderAboutPayment(Map params) {
        /*查询订单的支付状态
        * 1.若订单状态为7,8则说明微信公众平台已经处理了该订单 完成了所有业务
        * 2.否则则说明微信公众平台未处理该订单 乘客端在付款后可以主动向微信公众平台发送查询订单支付状态的请求
        *   如果微信公众平台的支付状态为成功 则需要完成 receiveMessage接收异步通知中未完成的业务逻辑
        *   (更新订单id以及订单状态 给司机钱包充值 分账 最后更新订单状态为结束订单等 4块业务)
        * */
        long orderId = MapUtil.getLong(params, "orderId");
        HashMap map = orderDao.searchUuidAndStatus(orderId);
        String uuid = MapUtil.getStr(map, "uuid");
        int status = MapUtil.getInt(map, "status");

        if (status==7 || status==8) {
            return "付款成功";
        }

        //向微信公众平台发送查询订单状态的请求
        //构造请求参数
        map.clear();
        map.put("appid", myWXPayConfig.getAppID());
        map.put("nonce_str", WXPayUtil.generateNonceStr());
        map.put("out_trade_no", uuid);
        map.put("mch_id", myWXPayConfig.getMchID());

        //构造数字签命
        try {
            String sign = WXPayUtil.generateSignature(map, myWXPayConfig.getKey());
            map.put("sign", sign);

            //构造wxPay对象执行发送请求
            WXPay wxPay = new WXPay(myWXPayConfig);
            Map result = wxPay.orderQuery(map);
            System.out.println(result);

            String returnCode = MapUtil.getStr(result, "return_code");
            String resultCode = MapUtil.getStr(result, "result_code");
            if ("SUCCESS".equals(resultCode) && "SUCCESS".equals(returnCode)) {
                //通信以及业务数据都成功
                Integer state = MapUtil.getInt(result, "trade_state");
                if ("SUCCESS".equals(state)) {
                    //说明付款状态为:付款成功 取出微信返回的关键参数
                    String payId = MapUtil.getStr(result, "transaction_id");
                    String driverOpenId = MapUtil.getStr(result, "attach");
                    String payTime = DateUtil.parse(MapUtil.getStr(result, "end_time"), "yyyyMMddHHmmss").toString("yyyy-HH-dd HH:mm:;ss");

                    //进行业务逻辑 更新订单id以及订单状态 给司机钱包充值 分账 最后更新订单状态为结束订单等 4块业务
                    params.put("payId", payId);
                    params.put("payTime", payTime);
                    params.put("driverOpenId", driverOpenId);

                    int rows = orderDao.updateOrderAboutPayment(params);
                    if (rows!=1) {
                        throw new GlobalException("更新订单相关信息失败");
                    }

                    //查询订单的系统奖励费以及司机id
                    HashMap hashMap = orderDao.searchDriverIdAndIncentiveFee(uuid);
                    String incentiveFee = MapUtil.getStr(hashMap, "incentiveFee");
                    Long driverId = MapUtil.getLong(hashMap, "driverId");
                    //如果系统奖励费大于0 则进行系统奖励
                    if (new BigDecimal(incentiveFee).compareTo(new BigDecimal("0.00"))==1) {
                        TransferForm transferForm = new TransferForm();
                        transferForm.setAmount(incentiveFee);
                        transferForm.setDriverId(driverId);
                        transferForm.setType((byte) 2);
                        transferForm.setUuid(uuid);
                        transferForm.setRemark("系统奖励费");
                        drServiceApi.transfer(transferForm);
                    }

                    //执行订单分账
                    //执行分账之前判断是否创建了分账定时器
                    if (quartzUtil.checkExists(uuid, "代驾单分账任务组") ||
                            quartzUtil.checkExists(uuid, "查询代驾单分账任务组")) {
                        //说明存在分账定时器 则不需要重复创建
                        return "付款成功";
                    }

                    //新建jobdetail对象放入分账的业务数据
                    JobDetail jobDetail = JobBuilder.newJob(HandleProfitsharingJob.class).build();
                    JobDataMap dataMap = jobDetail.getJobDataMap();
                    dataMap.put("uuid", uuid);
                    dataMap.put("payId", payId);
                    dataMap.put("driverOpenId", driverOpenId);
                    DateTime executeTime = new DateTime().offset(DateField.MINUTE, 2);//当前时间两分钟后执行分账
                    quartzUtil.addJob(jobDetail, uuid, "代驾单分账任务组", executeTime);

                    //完成订单(更改订单状态)
                    rows = orderDao.finishOrder(uuid);
                    if (rows!=1) {
                        throw new GlobalException("更新订单状态失败");
                    }

                }else{
                    return "付款失败";
                }
            }else {
                return "执行付款异常";
            }

        } catch (Exception e) {
            e.printStackTrace();
            throw new GlobalException("更新订单相关信息失败");
        }
        return "";
    }

    @Override
    public PageUtils searchDriverOrderByPage(Map map) {
        long count = orderDao.searchDriverOrderCount(map);
        ArrayList<HashMap> list = null;
        if (count == 0) {
            list = new ArrayList<>();
        }else{
            list = orderDao.searchDriverOrderByPage(map);
        }
        Integer length = MapUtil.getInt(map, "length");
        Integer start = MapUtil.getInt(map, "start");

        return new PageUtils(list,count,start,length);
    }

    @Override
    public PageUtils searchCustomerOrderByPage(Map map) {
        long count = orderDao.searchCustomerOrderCount(map);
        ArrayList<HashMap> list = null;
        if (count == 0) {
            list = new ArrayList<>();
        }else{
            list = orderDao.searchCustomerOrderByPage(map);
        }
        Integer length = MapUtil.getInt(map, "length");
        Integer start = MapUtil.getInt(map, "start");

        return new PageUtils(list,count,start,length);
    }


}
